package wmhsudoku.board;

import java.awt.Point;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

import wmhsudoku.gui.Main;

public class AnealingSolver extends Solver {
	
	protected static final double T_START = 0.6;
	
	private double t = T_START;
	private boolean tempChangeFunc = true;
	private double initTemp = 0.6;
	private double tempChange = 0.0000006;
	private int neighNumber = 1;
	private boolean neighChangeFunc = true;

	public AnealingSolver(Board board) {
		super(board);
		try {
			out = new BufferedWriter(new FileWriter("AnealingLog.txt"));
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
	
	public AnealingSolver(Board board, String logFileName, boolean tempChangeFunc, double initTemp, 
			double tempChange, boolean printConsole, boolean printFile, int neighNumber, boolean neighFunc) {
		super(board, printConsole, printFile);
		this.tempChangeFunc = tempChangeFunc;
		this.initTemp = initTemp;
		this.tempChange = tempChange;
		this.neighNumber = neighNumber;
		this.neighChangeFunc = neighFunc;
		
		try {
			out = new BufferedWriter(new FileWriter(logFileName));
		} catch (IOException e) {
			e.printStackTrace();
		} 		
	}

	@Override
	public void solve() {
		
		init();
		while (solveStep()); 
	}
	
	@Override
	public boolean solveStep() {
		++iterations;
		if (iterations == 40)
			++iterations;
		
		if (!Main.RUNNING)
			return false;
		
		// zmiana ilości zmienianych pól
		changeNeighNumber();


		Point[] newPoints = new Point[neighNumber];
		int[] newValues = new int[neighNumber];
		
		if (eMatrix.getError() > 0) {
			if (t <= 0.0) {
				t = initTemp;
			}
			
			// 2. przejście do sąsiada
			ErrorMatrix tmpEMatrix = new ErrorMatrix(eMatrix);
			
			// 3. wybór nowego stanu
			Map tmpMask = new Map(mask);
			for (int i=0; i<neighNumber; ++i) {
				newPoints[i] = chooseNeighbour(tmpMask);
				newValues[i] = chooseNeighValue(newPoints[i]);
				tmpEMatrix.updateErrorMatrix(newPoints[i], newValues[i], sudoku.getField(newPoints[i]));
			}
			
			tmpEMatrix.error();
			
			if (tmpEMatrix.getError() <= eMatrix.getError()) {		// przeskakujemy do sąsiada
				sudoku.updateSudoku(newPoints, newValues);
				eMatrix = tmpEMatrix;
			} else if (Math.random() < p(tmpEMatrix.getError()-eMatrix.getError())) {
				sudoku.updateSudoku(newPoints, newValues);
				eMatrix = tmpEMatrix;		
			} else {														// zostajemy w aktualnym
				// nic :-)
			}
			
			changeTemp();
			
			if (printConsole)
				System.out.println("i: " + iterations + " e: " + eMatrix.getError() + "  t: " +t);
			
			if (printFile) {
				try {
					out.write(iterations + "\t" + eMatrix.getError() + "\t" +t + "\n");
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			
			return true;
		}
		
		System.out.println("Liczba iteracji: " + iterations);
		try {
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}

	@Override
	public void init() {
		// 1. inicjalizacja wartości
		initSolution();
		super.init();
		t = initTemp;
	}

	private double p(double c) {
		double prob = 0;
		
		prob = Math.exp(-c / t);
		
		return prob;
	}
	
	private void changeTemp() {
		if (tempChangeFunc) {		// simple 
			if (tempChange == 0.0) {
				t -= 0.0000006;
			} else {
				t -= tempChange;
			}
		} else {		// exp
			if (initTemp == 0.0) {
				t = Math.exp(-1.0 * (double)iterations / 100000.0);
			} else {
				t = initTemp * Math.exp(-1.0 * (double)iterations / 100000.0);
			}
		}
	}
	
	private void changeNeighNumber() {
		if (neighChangeFunc) {	// constant
			return;
		} else {				
			if (iterations % 100000 == 0 && neighNumber > 1) {
				--neighNumber;
			}
		}
	}
	
	private Point chooseNeighbour(Map selected) {
		Point neigh = new Point();
		int row, col;
		
		do {
			row = (int)(Math.random()*9);
			col = (int)(Math.random()*9);
		} while (mask.getField(row, col) != 1 && selected.getField(row, col) != 1);
		
		neigh.x = row;
		neigh.y = col;
		selected.setField(neigh, 0);
		
		return neigh;
	}
	
	private int chooseNeighValue(Point coord) {
		int prevValue = sudoku.getField(coord);
		int value = 0;
		
		do {
			value = (int)(Math.random()*9 +1);
		} while (value == prevValue);
		
		return value;
	}

}
